package com.nulldev.util.concurrency.threadIt.v4.emapi;

import java.util.Deque;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.TimeUnit;

import com.nulldev.util.JVM.Arguments;
import com.nulldev.util.concurrency.threadIt.v3.extensions.Task;
import com.nulldev.util.concurrency.threadIt.v4.emapi.cft.CompleteableFutureTask;
import com.nulldev.util.concurrency.threadIt.v4.emapi.extensions.ExecutorFeatureExtensions;
import com.nulldev.util.concurrency.threadIt.v4.emapi.extensions.ExtendedExecutor;
import com.nulldev.util.concurrency.threadIt.v4.emapi.ft.FutureTask;
import com.nulldev.util.concurrency.threadIt.v4.emapi.ft2.FutureTask2;
import com.nulldev.util.concurrency.threadIt.v4.emapi.uft.Executable;
import com.nulldev.util.concurrency.threadIt.v4.emapi.uft.Returnable;
import com.nulldev.util.concurrency.threadIt.v4.sched.ASyncScheduler;
import com.nulldev.util.concurrency.threadIt.v4.utils.impls.AwaitTaskUtil;
import com.nulldev.util.concurrency.threadIt.v4.utils.impls.TEConditions;
import com.nulldev.util.internal.backport.concurrency9.Lists;

@SuppressWarnings("deprecation")
public interface IExecutorManager extends Executor {

	@Override
	default void execute(final Runnable command) {
		TEConditions.validRunnable(command);
		this.queueTask(command);
	}

	/**
	 * Submits a threadIt! v3.0 task.
	 * 
	 * @param <T>
	 * @param t   - Task
	 * @returns Same task that was submitted.
	 */
	public <T> Task<T> queueTask(final Task<T> t);

	/**
	 * Submits a Runnable as a task.
	 * 
	 * @param t - Runnable
	 */
	public void queueTask(final Runnable t);

	/**
	 * Submits a Runnable as a task. <br>
	 * Requires: EEX_core_execute_with_preference
	 * 
	 * @param t - Runnable to submit
	 * @param e - ExecutionPreference
	 */
	public void queueTask(final Runnable t, final ExecutionPreference e);

	/**
	 * Submits a FutureTask as a task to the executor.
	 * 
	 * @param <T>
	 * @param t   - FutureTask
	 * @returns FutureTask submitted
	 */
	public <T> FutureTask<T> queueTask(final FutureTask<T> t);

	/**
	 * Attempts to submit a low-priority FutureTask, else fallbacks to queueTask().
	 * 
	 * @param <T>
	 * @param t   - FutureTask
	 * @returns FutureTask submitted
	 */
	public default <T> FutureTask<T> queueTaskLP(final FutureTask<T> t) {
		TEConditions.validExecutable(t);
		return this.queueTask(t);
	}

	/**
	 * Submits a task of type Executable.
	 * 
	 * @param <T>
	 * @param t   - Executable
	 * @returns Executable submitted
	 */
	public <T> Executable<T> queueTask(final Executable<T> t);

	/**
	 * Attempts to submit a low-priority Executable, else fallbacks to queueTask().
	 * 
	 * @param <T>
	 * @param t   - Executable
	 * @returns Executable submitted
	 */
	public default <T> Executable<T> queueTaskLP(final Executable<T> t) {
		TEConditions.validExecutable(t);
		return this.queueTask(t);
	}

	/**
	 * Attempts to wait until the executor is idle. <br>
	 * Requires: EEX_executor_await_idle
	 */
	public void awaitIdle();

	/**
	 * Waits until ALL submitted Executables either complete or fail.
	 * 
	 * @param t - Executables
	 */
	public default void await(final Executable<?>... t) {
		if (t == null || t.length == 0) {
			return;
		} else {
			AwaitTaskUtil.await(t);
		}
	}

	/**
	 * Waits until ALL submitted Executables either complete or fail within a
	 * provided time.
	 * 
	 * @param amount
	 * @param unit
	 * @param t      - Executables
	 * @returns List of incomplete Executables
	 */
	public default List<Executable<?>> awaitFor(final long amount, final TimeUnit unit, final Executable<?>... t) {
		if (t == null || t.length == 0 || unit == null || amount <= 0) {
			return Lists.of();
		} else {
			return AwaitTaskUtil.awaitFor(amount, unit, t);
		}
	}

	/**
	 * Waits for a list of Executables to either complete or fail.
	 * 
	 * @param t - List of Executables
	 */
	public default void await(final List<Executable<?>> t) {
		if (t == null || t.isEmpty()) {
			return;
		} else {
			AwaitTaskUtil.await(t);
		}
	}

	public default <T> Executable<T> submit(final Returnable<T> r) {
		TEConditions.validReturnable(r);

		return this.queueTask(makeTask(r));
	}

	public default <T> Executable<T> submitLP(final Returnable<T> r) {
		TEConditions.validReturnable(r);

		return this.queueTaskLP(makeTask(r));
	}

	public default <T> Executable<T> submitNoAccel(final Returnable<T> r) {
		TEConditions.validReturnable(r);
		return this.queueTask(makeTaskNoAccel(r));
	}

	public default <T> Executable<T> submitLPNoAccel(final Returnable<T> r) {
		TEConditions.validReturnable(r);
		return this.queueTaskLP(makeTaskNoAccel(r));
	}

	public static <T> Executable<T> makeTaskNoAccel(final Returnable<T> r) {
		TEConditions.validReturnable(r);
		if (USE_STAGING_FT) {
			return new FutureTask2<T>() {

				@Override
				public T execute() {
					return r.run();
				}
			};
		} else {
			return new FutureTask<T>() {

				@Override
				public T execute() {
					return r.run();
				}
			};
		}
	}

	public static <T> Executable<T> makeTaskNoAccel(final Returnable<T> r, final int FLAGS) {
		TEConditions.validReturnable(r);

		if (USE_STAGING_FT) {
			return new FutureTask2<T>() {

				@Override
				public T execute() {
					return r.run();
				}

				@Override
				public Object getFlag(final int flag) {
					if ((FLAGS & flag) == flag)
						return FT_TRUE;
					return super.getFlag(flag);
				}

				@Override
				public int getFlags() {
					return FLAGS | super.getFlags();
				}
			};
		} else {
			return new FutureTask<T>() {

				@Override
				public T execute() {
					return r.run();
				}

				@Override
				public Object getFlag(final int flag) {
					if ((FLAGS & flag) == flag)
						return FT_TRUE;
					return super.getFlag(flag);
				}

				@Override
				public int getFlags() {
					return FLAGS | super.getFlags();
				}
			};
		}
	}

	public static final boolean USE_COMPLETEABLE_FUTURE = Arguments.hasArgument("--ExecutorManager.useCompletableFT");
	public static final boolean USE_STAGING_FT = Arguments.hasArgument("--ExecutorManager.useStagingFT");

	public default <T> Executable<T> makeTask(final Returnable<T> r) {
		TEConditions.validReturnable(r);
		if (USE_COMPLETEABLE_FUTURE) {
			return new CompleteableFutureTask<T>() {

				@Override
				public T execute() {
					return r.run();
				}
			};
		} else if (USE_STAGING_FT) {
			return new FutureTask2<T>() {

				@Override
				public T execute() {
					return r.run();
				}
			};
		} else {
			return new FutureTask<T>() {

				@Override
				public T execute() {
					return r.run();
				}
			};
		}
	}

	public default <T> void awaitQueue(final Queue<Executable<T>> _t) {
		if (_t == null || _t.isEmpty())
			return;
		this.await(_t.toArray(new Executable<?>[_t.size()]));
	}

	public default <T> List<Executable<?>> awaitQueueFor(final long amount, final TimeUnit unit, final Queue<Executable<T>> _t) {
		if (_t == null || _t.isEmpty())
			return Lists.of();
		return this.awaitFor(amount, unit, _t.toArray(new Executable<?>[_t.size()]));
	}

	public default <T> void awaitDeque(final Deque<Executable<T>> _t) {
		if (_t == null || _t.isEmpty())
			return;
		try {
			this.await(_t.toArray(new Executable<?>[_t.size()]));
		} catch (Exception e) {
			for (final Executable<?> t : _t) {
				t.waitTillCompletion();
			}
		}
	}

	public default <T> void awaitDeques(@SuppressWarnings("unchecked") final Deque<Executable<T>>... _t) {
		if (_t == null || _t.length == 0)
			return;
		try {
			for (final Deque<Executable<T>> t : _t) {
				if (t == null || t.isEmpty())
					continue;
				this.await(t.toArray(new Executable<?>[t.size()]));
			}
		} catch (Exception e) {
			for (final Deque<Executable<T>> t : _t) {
				if (t == null || t.isEmpty())
					continue;
				for (final Executable<T> ex : t)
					ex.waitTillCompletion();
			}
		}
	}

	public default <T> void awaitList(final List<Executable<T>> _t) {
		if (_t == null || _t.isEmpty())
			return;
		this.await(_t.toArray(new Executable<?>[_t.size()]));
	}

	public ExecutorService executor();

	public default Executable<?> asyncAwait(final Executable<?>... t) {
		if (t == null || t.length == 0)
			return null;
		return submit(() -> {
			await(t);
			return null;
		});
	}

	public default <T> Executable<T> asyncAwait(final Deque<Executable<T>> t) {
		if (t == null || t.isEmpty())
			return null;
		return submit(() -> {
			awaitDeque(t);
			return null;
		});
	}

	public default <T> Executable<T> asyncAwait(final Queue<Executable<T>> t) {
		if (t == null || t.isEmpty())
			return null;
		return submit(() -> {
			awaitQueue(t);
			return null;
		});
	}

	public default <T> Executable<T> asyncAwaits(@SuppressWarnings("unchecked") final Deque<Executable<T>>... t) {
		if (t == null || t.length == 0)
			return null;
		return submit(() -> {
			awaitDeques(t);
			return null;
		});
	}

	public default Executable<?> asyncAwait(final List<Executable<?>> t) {
		if (t == null || t.isEmpty())
			return null;
		return submit(() -> {
			await(t);
			return null;
		});
	}

	public default void setExecutor(final ExecutorService service) {
		throw new UnsupportedOperationException("Unimplemented unsafe operation!");
	}

	public default void asyncSubmit(final Runnable r) {
		TEConditions.validRunnable(r);
		this.queueTask(r);
	}

	public default void asyncSubmitAll(final Runnable... r) {
		if (r == null || r.length == 0)
			return;
		for (final Runnable _r : r)
			this.queueTask(_r);
	}

	public default <T> Executable<T> asyncSubmit(final Returnable<T> r) {
		TEConditions.validReturnable(r);
		return this.submit(r);
	}

	public default <T> Executable<T> asyncSubmit_NoAccel(final Returnable<T> r) {
		TEConditions.validReturnable(r);
		return this.submitNoAccel(r);
	}

	public default <T> Executable<T> autoAsyncSubmit(final boolean enable, final Returnable<T> r) {
		TEConditions.validReturnable(r);
		if (enable) {
			return this.asyncSubmit(r);
		} else {
			return this.submit(r);
		}
	}

	public default <T> Executable<T> tryAutoAsyncSubmit(final boolean enable, final Returnable<T> r) {
		TEConditions.validReturnable(r);
		if (enable) {
			return this.tryToAsyncSubmit(r);
		} else {
			return this.tryToSubmit(r);
		}
	}

	public <T> Executable<T> tryToAsyncSubmit(final Returnable<T> r);

	public <T> Executable<T> tryToSubmit(final Returnable<T> r);

	public default void autoAsyncSubmit(final boolean enable, final Runnable r) {
		TEConditions.validRunnable(r);
		if (enable) {
			this.asyncSubmit(r);
		} else {
			this.queueTask(r);
		}
	}

	public default <T> Executable<T> asyncSubmit(final Executable<T> r) {
		TEConditions.validExecutable(r);
		return this.queueTask(r);
	}

	public default boolean supportsNativeBatching() {
		return false;
	}

	public default boolean isWorkingAroundBatching() {
		return false;
	}

	public default boolean isWorkingAroundLevelledBatching() {
		return false;
	}

	public default boolean supportsTaskDrain() {
		return false;
	}

	public default boolean supportsSchedQueueBypass() {
		return false;
	}

	public default boolean isForking() {
		final ExecutorService ex = executor();
		return ex instanceof ForkJoinPool || ex instanceof com.nulldev.util.internal.backport.concurrency9.concurrent.ForkJoinPool
				|| (ex instanceof ExtendedExecutor && ((ExtendedExecutor) ex).supportsFeature(ExecutorFeatureExtensions.EEX_query_executor_forking)
						&& ((ExtendedExecutor) ex).executorIsForking());
	}

	public boolean isRunning();

	public default ASyncScheduler scheduler() {
		return null;
	}

}
